﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Phoenix.Diagnostics;
using Phoenix.Frames;

namespace Phoenix
{
    public abstract class PhoenixApplication : Application, IPhoenixHostTarget, IAppInfo, IShareDiagnosticInformation
    {
        readonly Dictionary<object, bool> appBarEnabledLookup = new Dictionary<object, bool>();
        readonly ApplicationBarBinder appBarBinder = new ApplicationBarBinder();
        readonly PhoneApplicationFrame rootFrame;
        readonly bool phoneApplicationInitialized;
        INavigationFrame applicationFrame;
        readonly object backLock = new object();
        bool shellDeactivated;

        // So the VS Designer doesn't complain
        // ReSharper disable PublicConstructorInAbstractClass
        public PhoenixApplication()
        {
            if (phoneApplicationInitialized)
                return;

            rootFrame = new PhoneApplicationFrame();
            rootFrame.Navigated += CompleteInitialisePhoneApplication;
            rootFrame.NavigationFailed += RootFrameNavigationFailed;
            rootFrame.BackKeyPress += RootFrameBackKeyPress;
            phoneApplicationInitialized = true;
        }

        void RootFrameOnNavigated(object sender, NavigationEventArgs navigationEventArgs)
        {
            if (navigationEventArgs.Content == Shell && shellDeactivated)
                applicationFrame.ActivateCurrentRequest();
            else if (navigationEventArgs.Content != Shell && !shellDeactivated)
            {
                shellDeactivated = true;
                applicationFrame.DeactivateCurrentRequest();
            }
        }

        // ReSharper restore PublicConstructorInAbstractClass
        void CompleteInitialisePhoneApplication(object sender, NavigationEventArgs e)
        {
            RootVisual = rootFrame;
            rootFrame.Navigated -= CompleteInitialisePhoneApplication;
            rootFrame.Navigated += RootFrameOnNavigated;
            PhoneApplicationService.Current.Deactivated += OnDeactivated;

            var frameworkElement = e.Content as Control;
            ContentHost = frameworkElement;
            Shell.OrientationChanged += (o, args) =>
            {
                var phonePage = applicationFrame.CurrentRequest.View as PhoneApplicationPage;
                if (phonePage != null)
                {
                    phonePage.Orientation = args.Orientation;
                }

                var phoenixPage = applicationFrame.CurrentRequest.View as PhoenixPage;
                if (phoenixPage != null)
                {
                    phoenixPage.OnOrientationChanged(args);
                }
            };

            InitialisePhoenix(frameworkElement, e)
                .ContinueWith(t =>
                                  {
                                      PhoenixHost = t.Result;
                                      LittleWatson.ReportPreviousException();
                                  });
        }

        protected void HandleDeepLinks(Uri navigationUrl, PhoenixHostBuilder hostBuilder)
        {
            if (navigationUrl.OriginalString.Contains("?"))
            {
                var arguments = navigationUrl.OriginalString
                        .Split(new[] { '?' })[1]
                        .Split(new[] { '&' })
                        .Select(o => o.Split(new[] { '=' }))
                        .ToDictionary(k => k[0], k => k[1]);

                if (arguments.ContainsKey("deepLink"))
                {
                    hostBuilder.Configuration.DefaultControllerAction = HttpUtility.UrlDecode(arguments["deepLink"]);
                }
            }
        }

        public IPhoenixHost PhoenixHost { get; private set; }

        protected virtual Task<IPhoenixHost> InitialisePhoenix(FrameworkElement content, NavigationEventArgs navigationEventArgs)
        {
            var hostBuilder = new PhoenixHostBuilder(
                TaskScheduler.FromCurrentSynchronizationContext(),
                Deployment.Current.Dispatcher);
            ConfigurePhoenixHostBuilder(hostBuilder);

            HandleDeepLinks(navigationEventArgs.Uri, hostBuilder);
            return hostBuilder.Run(this);
        }

        protected abstract void ConfigurePhoenixHostBuilder(IPhoenixHostBuilder hostBuilder);

        public void Show(INavigationFrame navigationFrame)
        {
            applicationFrame = navigationFrame;
            applicationFrame.NavigationComplete += DefaultFrameOnNavigationComplete;
            applicationFrame.ForegroundOperationStarted += DefaultFrameOnForegroundOperationStarted;
            applicationFrame.ForegroundOperationComplete += DefaultFrameOnForegroundOperationComplete;
            Shell.Content = navigationFrame as UIElement;
        }

        public INavigationFrame NavigationFrame { get { return applicationFrame; } }

        public Control ContentHost { get; private set; }

        void RootFrameBackKeyPress(object sender, CancelEventArgs e)
        {
            if (rootFrame.Content == ContentHost)
            {
                lock(backLock)
                {
                    if (applicationFrame.CanGoBack)
                    {
                        applicationFrame.NavigateBack();
                        e.Cancel = true;
                    }
                }
            }
        }

        public PhoneApplicationPage Shell
        {
            get { return ContentHost as PhoneApplicationPage; }
        }

        void DefaultFrameOnForegroundOperationComplete(object sender, EventArgs eventArgs)
        {
            if (Shell.ApplicationBar != null)
            {
                foreach (var button in Shell.ApplicationBar.Buttons.Cast<ApplicationBarIconButton>().Where(button => appBarEnabledLookup.ContainsKey(button)))
                {
                    button.IsEnabled = appBarEnabledLookup[button];
                }

                foreach (var menuItem in Shell.ApplicationBar.MenuItems.Cast<ApplicationBarMenuItem>().Where(menuItem => appBarEnabledLookup.ContainsKey(menuItem)))
                {
                    menuItem.IsEnabled = appBarEnabledLookup[menuItem];
                }
            }
            appBarEnabledLookup.Clear();
        }

        void DefaultFrameOnForegroundOperationStarted(object sender, EventArgs eventArgs)
        {
            if (Shell.ApplicationBar == null) return;

            foreach (ApplicationBarIconButton button in Shell.ApplicationBar.Buttons)
            {
                appBarEnabledLookup.Add(button, button.IsEnabled);
                button.IsEnabled = false;
            }

            foreach (ApplicationBarMenuItem menuItem in Shell.ApplicationBar.MenuItems)
            {
                appBarEnabledLookup.Add(menuItem, menuItem.IsEnabled);
                menuItem.IsEnabled = false;
            }
        }

        void DefaultFrameOnNavigationComplete(object sender, NavigationCompleteEventArgs args)
        {
            var page = args.NavigationRequest.View as PhoneApplicationPage;
            if (page == null) return;

            Shell.SupportedOrientations = page.SupportedOrientations;
            Shell.Orientation = page.Orientation;
            Shell.ApplicationBar = page.ApplicationBar;
            if (page.ApplicationBar == null) return;
            var viewModel = page.DataContext as ViewModelBase;
            if (viewModel != null) 
                appBarBinder.BindApplicationBar(Shell.ApplicationBar, viewModel);

            var shellElement = Shell as DependencyObject;
            if (shellElement != null)
            {
                TransferSystemTraySettings(page, shellElement);
            }
        }

        static void TransferSystemTraySettings(PhoneApplicationPage page, DependencyObject shellElement)
        {
            SystemTray.SetBackgroundColor(shellElement, SystemTray.GetBackgroundColor(page));
            SystemTray.SetForegroundColor(shellElement, SystemTray.GetForegroundColor(page));
            SystemTray.SetIsVisible(shellElement, SystemTray.GetIsVisible(page));
            SystemTray.SetOpacity(shellElement, SystemTray.GetOpacity(page));
            SystemTray.SetProgressIndicator(shellElement, SystemTray.GetProgressIndicator(page));
        }

        public void SetupSystemTray(PhoneApplicationPage page)
        {
            if (ContentHost == null) return;

            TransferSystemTrayToShell(SystemTray.BackgroundColorProperty, page, ContentHost);
            TransferSystemTrayToShell(SystemTray.ForegroundColorProperty, page, ContentHost);
            TransferSystemTrayToShell(SystemTray.IsVisibleProperty, page, ContentHost);
            TransferSystemTrayToShell(SystemTray.OpacityProperty, page, ContentHost);
        }

        private static void TransferSystemTrayToShell(DependencyProperty dependencyProperty, FrameworkElement page, FrameworkElement target)
        {
            var bindingExpression = page.GetBindingExpression(dependencyProperty);
            if (bindingExpression != null)
                target.SetBinding(dependencyProperty, bindingExpression.ParentBinding);
            else
            {
                var localValue = page.ReadLocalValue(dependencyProperty);
                if (localValue != DependencyProperty.UnsetValue)
                    target.SetValue(dependencyProperty, localValue);
            }
        }

        void OnDeactivated(object sender, DeactivatedEventArgs deactivatedEventArgs)
        {
            if (applicationFrame.CurrentRequest != null)
                applicationFrame.CurrentRequest.Deactivate();                
            applicationFrame.Journal.Save(PhoenixHost.ApplicationTransientState);
        }

        void RootFrameNavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            
        }

        public string Title
        {
            get { return Information.GetAppAttribute("Title"); }
        }

        public string Version
        {
            get { return Information.GetAppAttribute("Version"); }
        }

        public virtual string DiagnosticInformation
        {
            get { return null; }
        }

        public abstract string DeveloperEmailAddress { get; }
        public abstract void WriteTrace(string formattedMessage, string category);
    }
}